import { posix as posixPath } from 'path'
import * as Path from 'path'
import {
  Alias,
  BuildOptions,
  DepOptimizationOptions,
  InlineConfig,
  InternalResolveOptions,
  Logger,
  Plugin,
  ResolvedServerOptions,
  ResolveFn,
  ResolveOptions,
  UserConfig,
} from 'vite'
import { MODULE_IDS, MODULE_CURRENT_PAGE_ID, MODULE_ID_VIRTUAL } from './constants'
import { PageFS, transformDynamicRoute } from './fs'
import { generateClientRouteInfo, generateClientRoutesCode } from './generate'
import { resolveOptions } from './options'
import { ResolvedOptions, Route, UserOptions } from './types'
import { debug, normalizePath } from './utils'

export * from './types'
export { AnyValueWrapper, createAnyValueStringify } from './utils'
export default function pagesPlugin(userOptions: UserOptions = {}): Plugin {
  let generatedRoutes: Route[]
  let config: Readonly<
    Omit<UserConfig, 'plugins' | 'alias' | 'dedupe' | 'assetsInclude' | 'optimizeDeps'> & {
      configFile: string | undefined
      configFileDependencies: string[]
      inlineConfig: InlineConfig
      root: string
      base: string
      publicDir: string
      command: 'build' | 'serve'
      mode: string
      isProduction: boolean
      env: Record<string, any>
      resolve: ResolveOptions & { alias: Alias[] }
      plugins: readonly Plugin[]
      server: ResolvedServerOptions
      build: Required<
        Omit<BuildOptions, 'base' | 'cleanCssOptions' | 'polyfillDynamicImport' | 'brotliSize'>
      >
      assetsInclude: (file: string) => boolean
      logger: Logger
      createResolver: (options?: Partial<InternalResolveOptions> | undefined) => ResolveFn
      optimizeDeps: Omit<DepOptimizationOptions, 'keepNames'>
    }
  >
  let options: ResolvedOptions
  let pageFs: PageFS
  const importers: string[] = []
  const prefix = 'PAGES:'
  const suffix = '.current-page.tsx'

  return {
    name: 'vite-plugin-route-pages',
    enforce: 'pre',
    async configResolved(_config) {
      config = _config
      options = resolveOptions(userOptions, config.root, config.command === 'build')
      debug.options(options)

      pageFs = new PageFS(options)
      const { getRoutes, stop } = await pageFs.watch()
      generatedRoutes = getRoutes()

      if (config.command === 'build') {
        stop()
      } else {
        pageFs.on('reload', () => {
          generatedRoutes = getRoutes()
        })
      }
    },
    configureServer(server) {
      const { moduleGraph } = server
      pageFs.on('reload', () => {
        const module = moduleGraph.getModuleById(MODULE_ID_VIRTUAL)
        if (module) {
          moduleGraph.invalidateModule(module)
        }
      })
    },
    resolveId(id, importer) {
      if (MODULE_IDS.includes(id) || MODULE_IDS.some((i) => id.startsWith(i))) {
        if (importer) {
          importers.push(importer)
        }
        return MODULE_ID_VIRTUAL
      }
      if (MODULE_CURRENT_PAGE_ID === id && importer) {
        return (
          prefix +
          normalizePath('/' + Path.relative(config.root, importer).split('?', 1)[0]) +
          suffix
        )
      }
      return null
    },
    load(id) {
      if (id === MODULE_ID_VIRTUAL) {
        const clientCode = generateClientRoutesCode(generatedRoutes, options)
        debug.gen('client routes code: %O', clientCode)

        return clientCode
      } else if (id.startsWith(prefix) && id.endsWith(suffix)) {
        let _id = id.substring(prefix.length)
        _id = _id.substring(0, _id.length - suffix.length)
        if (_id) {
          const clientCode = generateClientRouteInfo(generatedRoutes, _id)

          debug.gen('client route-info code: %O', clientCode)
          return clientCode
        }
      }
    },
    async transform(_code, id) {
      if (!/vue&type=route/.test(id)) return
      return {
        code: 'export default {}',
        map: null,
      }
    },
    outputOptions(outputOptions) {
      if (options.replaceSquareBrackets) {
        const { chunkFileNames = '[name]-[hash].js', entryFileNames = '[name]-[hash].js' } =
          outputOptions
        if (typeof chunkFileNames === 'string') {
          outputOptions.chunkFileNames = ({ name }) => {
            name = transformDynamicRoute(name)
            return chunkFileNames.replace(/\[name\]/g, name)
          }
        } else {
          outputOptions.chunkFileNames = function (chunk) {
            chunk.name = transformDynamicRoute(chunk.name)
            return chunkFileNames.call(this, chunk)
          }
        }

        if (typeof entryFileNames === 'string') {
          outputOptions.entryFileNames = ({ name }) => {
            name = transformDynamicRoute(name)
            return entryFileNames.replace(/\[name\]/g, name)
          }
        } else {
          outputOptions.entryFileNames = function (chunk) {
            chunk.name = transformDynamicRoute(chunk.name)
            return entryFileNames.call(this, chunk)
          }
        }
      }
      return undefined
    },
  }
}
